/**
 * \file  antiviral_set.h
 * \brief Definition of Antiviral Set implementation class.
 *
 * LAV: Lib Anti Virals
 * <http://lav.googlecode.com/>
 * Copyright (C) 2010 Velez Ezequiel, Peralta Andres,
 * 				 2013  Boretto Martin, Lenarduzzi Federico, 
 *                    Alberione Fernando, Ocampo Emanuel FuDePAN
 *
 * This file is part of the LAV project.
 *
 * Contents:       Header file for LAV providing class Antiviral implementation.
 *
 * System:         LAV
 * Language:       C++
 *
 * Author:         Velez Ezequiel, Peralta Andres
 * E-Mail:
 *
 * Author:         Boretto Martin
 * E-Mail:         martinboretto@gmail.com
 *
 * Author:         Lenarduzzi Federico
 * E-Mail:         federicolenarduzzi@gmail.com
 * 
 * Author:         Alberione Fernando
 * E-Mail:         falberione037@gmail.com
 *
 * Author:         Ocampo Emanuel
 * E-Mail:         eocampo86@gmail.com
 *
 * LAV is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * LAV is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with LAV.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

#ifndef ANTIVIRAL_SET_H
#define ANTIVIRAL_SET_H

#include <mili/mili.h>
#include "lav/antiviral_imp.h"
using namespace std;

namespace lav
{


/** This is used to rank antivirals */
const double TOLERANCE = 1.0;

/** INVALID_TRIPLET is a value that does not represent any triplet (mixed base number) */
const uint INVALID_TRIPLET = 0;


/** This typedef represents an antiviral's set */
typedef std::set<ResisBasedAntiviral*> ArvsSet;


/** This typedef represents an mutants stack */ 
typedef std::list<PossibleMutants::Id> MutantsStack;

/** This typedef represents an list of antiviral's */
typedef std::list<ResisBasedAntiviral> ResisBasedAntiviralContainer;

/**
 * This struct represents an expanded resistance position (in nucleotids)
 */
struct ExpandedResistance
{
    size_t pos;
    biopp::Triplet triplet;

    /**
     * Constructor of ExpandedResistance
     */
    ExpandedResistance(size_t p, biopp::Triplet t):pos(p), triplet(t)
    {}


    /**
     * This method defines the equivalence operator for this struct
     * @param other the resistance to will compare
     * @return true or false
     */
    inline bool operator== (const ExpandedResistance& other);
};


/** This typedef represents a list of triplets to which this aminocid expands */
typedef std::list <ExpandedResistance> AminoacidExpansion;


/** This typedef represents a list of expanded aminoacids, that is an ARV expansion */
typedef std::list <AminoacidExpansion> ARVExpansion;


/** This typedef represents a list of expanded ARVs, that is a list of arv expansion */
typedef std::list <ARVExpansion> ARVExpansionContainer;


/**
 * This struct represents a list of (pos, triplets) with the combination's distance
 */
struct CombinationDistance
{
    AminoacidExpansion triplets_combinations;
    biopp::NucDistance distance;

    /**
     * Constructor of CombinationDistance
     */
    CombinationDistance(const AminoacidExpansion& other_triplets, biopp::NucDistance distance_other)
    :triplets_combinations(other_triplets), distance(distance_other)
    {}


    /**
     * This redefinition of == operator is neccesary to sort all combinations by distances
     * @param other Combination to will be compared with the current one
     */
    inline bool operator== (const CombinationDistance& other);


    /**
     * This redefinition of < operator is neccesary to sort all combinations by distances
     * @param other Combination to will be compared with the current one
     */
    inline bool operator< (const CombinationDistance& other);
};


/** This typedef represents a list of (combinations, distance) to will be used for the ranking */
typedef std::list <CombinationDistance> CombinationDistanceContainer;


/** This typedef defines a combination and the mutant sequence generated */
typedef std::pair<CombinationDistance, biopp::PseudoNucSequence> Mutant;


/** This typedef defines list of mutants */
typedef std::list <Mutant> MutantContainer;

/** This typedef defines a set antivirals iterator */
typedef mili::CAutonomousIterator<ArvsSet> AVIterator;


/**
 * This class represents all information and methods for a set of antivirals
 */
class AntiviralSet
{
    ArvsSet antivirals;
public:

    AntiviralSet();


    /**
     * This class contains all methods according to combinations of antivirals
     */
    class Combinator
    {
        ArvsSet& antivirals;
    public:

        Combinator(ArvsSet& avrs):antivirals(avrs)
        {}


        /**
         * This method reduces redundant and unnecessary ARVs information ( STEP 1 - AProba Algorithm )
         * @param sequence represents patient's virus
         */
        void pre_processing(const biopp::PseudoNucSequence& sequence) const;


        /**
         * This method generates power set for each antiviral's group  ( STEP 2 and 3 - AProba Algorithm )
         * @param power_set_group_generated It will contain the pair of elements list
         */
        void generate_power_set(ResisBasedAntiviral::CombinationContainer& power_set_group_generated) const;


        /**
         * This method generates the cartesian product between each antiviral's group combined ( STEP 4 - AProba Algorithm)
         * @param antivirals_minimazed Antiviral information to will be process
         * @param antivirals_x  The cartesian product will be here
         */
        void generate_cartesian_product(const ResisBasedAntiviral::CombinationContainer& antivirals_minimazed,
                                        ResisBasedAntiviral::CombinationContainer& antivirals_x) const;


        /**
         * This method expand each antiviral's combination to all codons representation. After that, generate the
         * cartesian product between combinations expansion. ( STEP 5 - AProba Algorithm)
         * @param sequence Represents the patient's virus
         * @param antivirals_x Represents the cartesian product between arvs represented in aminoacids
         * @param antivirals_expanded The combination's expansion result will be here
         */
        void expand_to_nucleotids(const biopp::PseudoNucSequence& sequence,
                                  const ResisBasedAntiviral::CombinationContainer& antivirals_x,
                                  ARVExpansionContainer& antivirals_expanded) const;


        /**
         * This method expand each antiviral's combination to all codons representation. After that, generate the
         * cartesian product between combinations expansion.
         * @param antivirals_expanded
         * @param antivirals_expanded_with_cp
         */
        void generate_triplets_cartesian_product(const ARVExpansionContainer& antivirals_expanded,
                                            ARVExpansionContainer& antivirals_expanded_with_cp) const;
											
       /**
        * This method combines the antiviral according to the policy combination
        * @param combination combination of antivirals
        */
        void combine_antivirals(ARVExpansionContainer& combination) const;											
		
    private:

        /**
         * This method updates redundant information from an arv
         * @param sequence Represents the patient's virus
         * @param it_av Represents an arv (current)
         */
        void update_arv_information(const biopp::PseudoNucSequence& sequence,
                                    ArvsSet::iterator& it_av) const;


        /**
         * This method calculate the power set for a group, and save this result into a list of combinations.
         * @param it_av Represents arvs iterator
         * @param combination_container_generated The power set for the current group will be here
         */
        void make_power_set(const ArvsSet::const_iterator& it_av,
                            ResisBasedAntiviral::CombinationContainer& combination_container_generated) const;


        /**
         * This method expands each aminoacid generated from the power set method, and save these expansions
         * into a first level list.
         * @param it_comb Represents the current combination to will be expanded
         * @param sequence Represents the patient's virus
         * @param aminoacid_expansion The result will be save in this list
         */
        void process_combinations(const ResisBasedAntiviral::Combination::const_iterator& it_comb,
                                  const biopp::PseudoNucSequence& sequence,
                                  AminoacidExpansion& aminoacid_expansion) const;


        /**
         * This method processing the first expansion level and save this combinations into a second level list.
         * @param it_comb_cont Represents the current first level combination to will be saved
         * @param sequence Represents the patient's virus
         * @param arv_expanded The result will be save in this list
         */
        void process_combinations_container(const ResisBasedAntiviral::CombinationContainer::const_iterator& it_comb_cont,
                                            const biopp::PseudoNucSequence& sequence,
                                            ARVExpansion& arv_expansion) const;


        /**
         * This method updates information's group from an arv
         * @param sequence Represents the patient's virus
         * @param it_av Represents an arv'group (current)
         */
        void update_group_information(const biopp::PseudoNucSequence& sequence,
                                      ResisBasedAntiviral::ResistancesGroupContainer::iterator& it_groups) const;  


        /**
         * This method pre_process "aux_antivirals_x" expanding each composite combination in simple combinations
         * to allow calculate the cartesian product using triplets.
         * @param aux_antivirals_x Represents the list of combinations.
         * @param antivirals_x The result will be save in this list.
         */
        void split_antivirals_x(const ResisBasedAntiviral::CombinationContainer& aux_antivirals_x,
                                ResisBasedAntiviral::CombinationContainer& antivirals_x) const;


        /**
         * This method expands each aminoacid generated from the power set method, and save these expansions
         * into a first level list.
         * @param it_resistances Represents the current combination to will be expanded
         * @param sequence Represents the patient's virus
         * @param aminoacid_expansion The result will be save in this list
         */
        void generate_pos_triplet_list(const ResisBasedAntiviral::ResistancePositionContainer::const_iterator& it_resistance,
                                    const biopp::PseudoNucSequence& sequence, AminoacidExpansion& aminoacid_expansion) const;
									
       /**
        * This method expands the combination of amin to nucleotides
        * @param antivirals combination in amin
        * @param antivirals_expanded combination in nucleotides
        */
        void expand_combination_to_nucleotide(const ResisBasedAntiviral::CombinationContainer& antivirals,
		                                     ARVExpansionContainer& antivirals_expanded) const;


		/**
		* This method expand aminoacid to triplets
		* @param it_resistance current resistence
		* @param amin_expansion aminoacid expansion
		*/
		void expand_aminoacids_to_triplets(const ResisBasedAntiviral::ResistancePositionContainer::const_iterator& it_resistance,
										AminoacidExpansion& amin_expansion) const;
    };

    /**
     * This class contains all methods according to ranking of antivirals
     */
    class Ranker
    {
        ArvsSet& antivirals;
    public:

        Ranker(ArvsSet& avrs):antivirals(avrs)
        {}


        /**
         * This method calculates all combinations distances to will be used to ranking ( STEP 6 - AProba Algorithm )
         * @param sequence Represents the patient's virus
         * @param matrix Represents the distances matrix
         * @param arvs_expansion Represents all combinations in nucleotids
         * @param combinations_with_distances The list of combinations distances will be here
         */
        void calculate_genetic_distance(const biopp::PseudoNucSequence& sequence, const biopp::DistanceMatrix& matrix,
                                    const ARVExpansionContainer& arvs_expansion,
                                    CombinationDistanceContainer& combinations_with_distances) const;


        /**
         * This method ranks combinatins according to distances and tolerance definied previously ( STEP 7 - AProba Algorithm )
         * @param combinations_with_distances Represents the unordered combinations
         * @param combinations_with_distances_reduce The sort of all distances combinations will be here
         */
        void rank_combinations(const CombinationDistanceContainer& combinations_with_distances,
                           CombinationDistanceContainer& combinations_with_distances_reduce) const;


    private:

        /**
         * This method calculate the genetic distance to a simple combination  of codons
         * @param sequence Represents the patient's virus
         * @param matrix Represents the genetic distance matrix
         * @param it_first Represents the current combination to calculate the genetic distance
         * @param distance_result The genetic distance result will be here
         */
        void calculate_current_genetic_distance(const biopp::PseudoNucSequence& sequence,
                                            const biopp::DistanceMatrix& matrix,
                                            const AminoacidExpansion::const_iterator& it_first,
                                            biopp::NucDistance& distance_result) const;

    };


    /**
     * This class contains all methods according to get mutants of antivirals
     */
    class Mutants
    {
        ArvsSet& antivirals;
    public:

        Mutants(ArvsSet& avrs):antivirals(avrs)
		{}


        /**
         * This method generate a list of mutants according the applicance with the sequence ( STEP 8 - AProba Algorithm )
         * @param combinations Represents all combinations with genetic distances reduced
         * @param mutants_generated The list of mutants will be here
         */
         void generate_mutants(const biopp::PseudoNucSequence& sequence,
                            const CombinationDistanceContainer& combinations,
                            MutantContainer& mutants_generated) const;  
							
       /**
        * This method join the resistances (aminoacid and triplets) of all antivirals according to their position
        * @param join_antivirals result of joining the resistance according to their position
        */
        void join_antivirals(PossibleMutants::PosAminoacidsTripletsContainer& join_antivirals) const;
		
		
       /**
        * This method returns antiviral applied to the mutant sequence
        * @param join_all_antivirals result of joining the resistance according to their position
        * @param possible_mutants all possible mutants
        * @param id mutant identifier
        * @param applicable_antivirals applied to the mutant sequence (virus)
        */
        void get_applicable(const PossibleMutants::PosAminoacidsTripletsContainer& join_all_antivirals,
                        const PossibleMutants& possible_mutants, const PossibleMutants::Id id,
                        AntiviralSet& applicable_antivirals) const;

							
       /**
        * This method returns the antiviral applied to a populations of mutants (ids)
        * @param ids mutant populations
        * @param applicable_antivirals intersection of antivirals that apply to the populations of mutants (ids)
        */
        void get_applicable_populations(const PossibleMutants::PopulationsIds& ids, AntiviralSet& applicable_antivirals) const;
		
		
	   /**
        * This method returns the id of the mutant sequence
        * @param join_all_antivirals result of joining the resistance according to their position
        * @param possible_mutants all possible mutants
        * @param sequence mutant sequence
        */
        PossibleMutants::Id get_id_from_sequence(const PossibleMutants::PosAminoacidsTripletsContainer& join_all_antivirals,
                        const PossibleMutants& possible_mutants, const biopp::NucSequence& sequence) const;
				

       /**
        * This method returns the mutant sequence representing the id
        * @param join_all_antivirals result of joining the resistance according to their position
        * @param possible_mutants all possible mutants
        * @param id mutant identifier
        * @param sequence mutant sequence
        */
        void get_sequence_from_id(const PossibleMutants::PosAminoacidsTripletsContainer& join_all_antivirals,
                        const PossibleMutants& possible_mutants, const PossibleMutants::Id id,
                        biopp::NucSequence& sequence) const;


       /**
        * This method returns the mutants obtained in the application of antivirals to the mutant
        * @param join_all_antivirals result of joining the resistance according to their position
        * @param possible_mutants all possible mutants
        * @param applicable_antivirals antiviral applied to the mutant sequence (virus)on
        * @param id mutant identifier
        * @param mutants mutants obtained in the application
        */
        void get_mutants(const PossibleMutants::PosAminoacidsTripletsContainer& join_all_antivirals, 
                        const PossibleMutants& possible_mutants, const AntiviralSet& applicable_antivirals,
                        const PossibleMutants::Id id, PossibleMutants::PopulationsIds& mutants) const;


       /** 
        * This method returns the union of the mutants obtained in the application of antivirals to the populations of mutants
        * @param ids mutant populations
        * @param applicable_antivirals intersection of antivirals that apply to the populations of mutants (ids)
        * @param all_mutants mutants obtained in the application
        */
        void get_mutants_populations(const PossibleMutants::PopulationsIds& ids, const AntiviralSet& applicable_antivirals,
                        PossibleMutants::PopulationsIds& all_mutants)const;


       /**
        * This method returns the mutants catch up from the id
        * @param id mutant identifier
        * @param mutants mutants reached
        */
        void get_mutants_catch_up(const PossibleMutants::Id id, PossibleMutants::PopulationsIds& mutants) const;
  
    private:

       /**
        * This method returns the information of antiviral
        * @param it_av current antiviral
        * @param join_resistence joining the resistance according to their position
        */
        void get_arv_information(const ArvsSet::const_iterator& it_av,
                        PossibleMutants::PosAminoacidsTripletsContainer& join_resistence) const;


       /**
        * This method returns the information antiviral groups
        * @param it_groups current group of antiviral
        * @param join_resistence joining the resistance according to their position
        */
        void get_group_information(const ResisBasedAntiviral::ResistancesGroupContainer::const_iterator& it_groups,
		                PossibleMutants::PosAminoacidsTripletsContainer& join_resistence) const;


       /**
        * This method adds resistance (aminoacid and triplets) to join according to their position
        * @param it_resistances current resistence of group
        * @param join_resistence joining the resistance according to their position
        */
        void add_resistence_to_join(const ResisBasedAntiviral::ResistancePositionContainer::const_iterator& it_resistances,
		               PossibleMutants::PosAminoacidsTripletsContainer& join_resistence) const;


       /**
        * This method returns aminoacids with their respective triplet
        * @param aminoacids aminoacids in resistance
        * @param aminoacids_triplets aminoacid with their respective triplets (codons)
        */
        void get_aminoacid_triplets(const std::string& aminoacids,
		                PossibleMutants::AminoacidTripletsContainer& aminoacids_triplets) const;


       /**
        * This method returns true if the aminoacid exists in this position
        * @param amin aminoacids
        * @param amin_triplets list of aminoacid in position
        */
        bool exist_amin(char amin, PossibleMutants::AminoacidTripletsContainer& amin_triplets) const;


       /**
        * This method applies combination to mutant
        * @param it_arv_exp_container represents combination in nucleotids
        * @param join_all_antivirals joining the resistance according to their position
        * @param possible_mutants all possible mutants
        * @param mutant mutant sequence
        * @param mutants mutants reached
        */
        void combination_apply(const ARVExpansionContainer::const_iterator& it_arv_exp_container,
                        const PossibleMutants::PosAminoacidsTripletsContainer& join_all_antivirals,
                        const PossibleMutants& possible_mutants, const PossibleMutants::MutantSeq& mutant, 
                        PossibleMutants::PopulationsIds& mutants) const;


       /**
        * This method applies resistance to mutant
        * @param it_exp_resis current resistence
        * @param join_antivirals joining the resistance according to their position
        * @param current_mutant current mutant
        */
        void resistance_apply(const AminoacidExpansion::const_iterator& it_exp_resis,
		                const PossibleMutants::PosAminoacidsTripletsContainer& join_antivirals,
                        PossibleMutants::MutantSeq& current_mutant) const;


       /**
        * This method verifies the existence of the id in the result set
        * @param id identifier of mutant
        * @param mutants current mutant (ids)
        */
        bool exist_id(const PossibleMutants::Id id, const PossibleMutants::PopulationsIds& mutants) const;


       /**
        * This method makes the intersection of the sets of ARVs
        * @param current_arvs_applies current antivirals
        * @param antivirals_applies  antivirals applies (result)
        */
        void intersection_set(const AntiviralSet& current_arvs_applies, AntiviralSet& antivirals_applies) const;


       /**
        * This method gets the mutants catch up from current_id
        * @param join_all_antivirals joining the resistance according to their position
        * @param possible_mutants all possible mutants
        * @param current_id identifier of current mutant 
        * @stack_ids mutants stack catch up
        */
        void get_catch_up(const PossibleMutants::PosAminoacidsTripletsContainer& join_all_antivirals,
                        const PossibleMutants& possible_mutants, const PossibleMutants::Id current_id,
                        MutantsStack& stack_ids) const;
    };

    /** Attributes of the class AntiviralSet */
    Combinator combinator;
    Ranker ranker;
    Mutants mutants;

	public:

		/**
		* This method returns the beggining of the antivirals list
		* @return beggining of the antivirals list
		*/
		ArvsSet::iterator begin() const;


		/**
		* This method returns the ending of the antivirals list
		* @return ending of the antivirals list
		*/
		ArvsSet::iterator end() const;


		/**
		* This method returns size of the antiviral's list
		* @return antiviral size
		*/
		size_t size() const;


		/**
		* This method returns beggining of the antivirals list
		* @return AVIterator
		*/
		AVIterator begin_AVIterator() const;


		/**
		* This method make an antiviral copy to another one
		* @param new_avs antiviral to will be cloned
		*/
		void clone(AntiviralSet& new_avs) const;


		/**
		* This method returns an id list from each antiviralSet
		* @param res_list id list from antiviral
		*/
		void to_id_list(AntiviralIdentifierContainer& res_list) const;


		/**
		* This method removes an antiviral from the list
		* @param id_to_rem antiviral id to will be removed
		*/
		void remove(const AntiviralIdentifierContainer& id_to_rem);


		/**
		* This method removes an antiviral if exists
		* @param id_to_rem antiviral id to will be removed
		*/
		void remove_id(AntiviralIdentifier id_to_rem);


		/**
		* This method gets the iterator according to the parameter position.
		* @param id_target Position to will be search
		* @return iterator in the id_target position
		*/
		ArvsSet::iterator find(AntiviralIdentifier id_target) const;


		/**
		* This method remove an antiviral from the list
		* @param antiviral Antiviral to will be removed
		*/
		void remove(ResisBasedAntiviral* antiviral);


		/**
		* This method gets a list with the antivirals according to 'ids' list parameter
		* @param ids Ids list antivirals
		* @param selection The result will be saved here
		*/
		void select(const AntiviralIdentifierContainer& ids, AntiviralSet& selection) const;


		/**
		* This method add an antiviral to the list
		* @param antiviral Antiviral to will be added
		*/
		void add_antiviral(ResisBasedAntiviral& antiviral);		

	private:

		/**
		* This method gets a sequence list mutants according to applies the antivirals set to the sequence
		* @param sequence Represents the patient's virus
		* @param mutant_list The result will be saved here
		*/
		void observe_mutants(const biopp::PseudoNucSequence& sequence, MutantDist& mutant_list);	


		/**
		* This method combines an antiviral set
		* @param combined Antiviral to will be combined
		*/
		void combine_set(ResisBasedAntiviral& combined);


		/**
		* This method prints the names from the antivirals list
		* @param out The result will be saved here
		*/
		void get_info(AntiviralsNames& out) const;


		/**
		* This method splits antivirals by type
		* @param nrti
		* @param nnrti
		* @param pi
		*/
		void split_antivirals_by_type(ResisBasedAntiviralContainer& nrti, ResisBasedAntiviralContainer& nnrti, 
									ResisBasedAntiviralContainer& pi) const;


		/**
		* This method defines the equivalence operator
		* @param other The antiviral to will compare
		* @return true or false
		*/
		inline bool operator==(const AntiviralSet& other) const;


		/**
		* This method serialize an antiviralSet
		* @param bos
		* @param avs
		*/
		friend inline mili::bostream<>& operator<< (mili::bostream<>& bos, const AntiviralSet& avs);


		/**
		* This method deserialize an antiviralSet
		* @param bis
		* @param avs
		*/
		friend inline mili::bistream<>& operator>> (mili::bistream<>& bis, AntiviralSet& avs);
	};
}

#define ANTIVIRAL_SET_INLINE_H
#include "antiviral_set_inline.h"
#undef ANTIVIRAL_SET_INLINE_H

#endif